
#include "rbfm.h"

RecordBasedFileManager *RecordBasedFileManager::_rbf_manager = nullptr;

RecordBasedFileManager &RecordBasedFileManager::instance() {
    if (!_rbf_manager) {
        _rbf_manager = new RecordBasedFileManager();
    }
    return *_rbf_manager;
}

RecordBasedFileManager::RecordBasedFileManager() = default;

RecordBasedFileManager::~RecordBasedFileManager() {
    if (_rbf_manager) {
        delete _rbf_manager;
        _rbf_manager = nullptr;
    }
}

RecordBasedFileManager::RecordBasedFileManager(const RecordBasedFileManager &) = default;

RecordBasedFileManager &RecordBasedFileManager::operator=(const RecordBasedFileManager &) = default;

RC RecordBasedFileManager::createFile(const std::string &fileName) {
    return PagedFileManager::instance().createFile(fileName);
}

RC RecordBasedFileManager::destroyFile(const std::string &fileName) {
    return PagedFileManager::instance().destroyFile(fileName);
}

RC RecordBasedFileManager::openFile(const std::string &fileName, FileHandle &fileHandle) {
    return PagedFileManager::instance().openFile(fileName, fileHandle);
}

RC RecordBasedFileManager::closeFile(FileHandle &fileHandle) {
    return PagedFileManager::instance().closeFile(fileHandle);
}

short RecordBasedFileManager::getRecordCount(const void *page) {
    short recordCount;
    memcpy(&recordCount, page, sizeof(short));
    return recordCount;
}

void RecordBasedFileManager::setRecordCount(const void *page, short recordCount) {
    memcpy((char *) page, &recordCount, sizeof(short));
}

short RecordBasedFileManager::getSlotCount(const void *page) {
    short slotCount;
    memcpy(&slotCount, (char *) page + sizeof(short), sizeof(short));
    return slotCount;
}

void RecordBasedFileManager::setSlotCount(const void *page, short slotCount) {
    memcpy((char *) page + sizeof(short), &slotCount, sizeof(short));
}

short RecordBasedFileManager::getRecordOffset(const void *page, short slotNum) {
    short recordOffset;
    int ptr = 2 * sizeof(short) + 2 * slotNum * sizeof(short);
    memcpy(&recordOffset, (char *) page + ptr, sizeof(short));
    return recordOffset;
}

void RecordBasedFileManager::setRecordOffset(const void *page, short slotNum, short recordOffset) {
    int ptr = 2 * sizeof(short) + 2 * slotNum * sizeof(short);
    memcpy((char *) page + ptr, &recordOffset, sizeof(short));
}

short RecordBasedFileManager::getRecordSize(const void *page, short slotNum) {
    short recordSize;
    int ptr = 2 * sizeof(short) + 2 * slotNum * sizeof(short) + sizeof(short);
    memcpy(&recordSize, (char *) page + ptr, sizeof(short));
    return recordSize;
}

void RecordBasedFileManager::setRecordSize(const void *page, short slotNum, short recordSize) {
    int ptr = 2 * sizeof(short) + 2 * slotNum * sizeof(short) + sizeof(short);
    memcpy((char *) page + ptr, &recordSize, sizeof(short));
}

short RecordBasedFileManager::findUnusedSlot(const void *page) {
    short slotCount = this->getSlotCount(page);
    short slotNum = 0;
    while (slotNum < slotCount) {
        if (this->getRecordSize(page, slotNum) == Unused) {
            break;
        }
        slotNum++;
    }
    return slotNum;
}

short RecordBasedFileManager::getInsertOffset(const void *page, short freeSpace, short recordSize) {
    return 2 * sizeof(short) + this->getSlotCount(page) * 2 * sizeof(short) + freeSpace - recordSize;
}

void RecordBasedFileManager::getAttrOffset(const void *page, short pagePtr, int fieldCount, short nullFlagSize,
                                           short attrIdx, short &offset, short &prevOffset) {
    memcpy(&offset, (char *) page + pagePtr + nullFlagSize + attrIdx * sizeof(short), sizeof(short));
    prevOffset = nullFlagSize + fieldCount * sizeof(short);
    if (attrIdx != 0) {
        memcpy(&prevOffset, (char *) page + pagePtr + nullFlagSize + (attrIdx - 1) * sizeof(short), sizeof(short));
    }
}

short RecordBasedFileManager::getNullFlagSize(int fieldCount) {
    return ceil((double) fieldCount / CHAR_BIT);
}

short RecordBasedFileManager::parseRecord(const std::vector<Attribute> &recordDescriptor, const void *data,
                                          const void *offsets) {
    int fieldCount = recordDescriptor.size();
    int curr = getNullFlagSize(fieldCount);
    short fullRecordSize = curr + sizeof(short) * fieldCount; // the size after adding offsets

    int nullInfoLen = getNullFlagSize(fieldCount);
    char nullInfos[nullInfoLen];
    memcpy(nullInfos, data, nullInfoLen);

    for (int i = 0; i < fieldCount; ++i) {

        int bytePos = i / 8;
        int bitPos = i % 8;
        bool nullBit = nullInfos[bytePos] & (unsigned) 1 << (unsigned) (7 - bitPos);

        Attribute attr = recordDescriptor[i];
        AttrValue attrValue;
        if (!nullBit) {
            attrValue.readAttr(attr.type, (char *) data + curr);
            curr += attrValue.length;
            fullRecordSize += attrValue.length;
        }
        memcpy((char *) offsets + i * sizeof(short), &fullRecordSize, sizeof(short));

    }
    return fullRecordSize;
}

RC RecordBasedFileManager::insertFormatedRecord(const void *page, short insertPtr, int fieldCount,
                                                const void *data, const void *offsets, short recordSize) {
    short nullFlagSize = this->getNullFlagSize(fieldCount);
    short dataPtr = 0;
    short pagePtr = insertPtr;
    memcpy((char *) page + pagePtr, (char *) data + dataPtr, nullFlagSize);
    dataPtr += nullFlagSize;
    pagePtr += nullFlagSize;
    memcpy((char *) page + pagePtr, (char *) offsets, fieldCount * sizeof(short));
    pagePtr += fieldCount * sizeof(short);
    short sz = recordSize - (pagePtr - insertPtr);
    memcpy((char *) page + pagePtr, (char *) data + dataPtr, sz);
    return 0;
}

//move to right, distance>0,else distance<0
RC RecordBasedFileManager::shiftRecord(const void *page, short recordOffset, short distance) {
    short slotCount = this->getSlotCount(page);
    bool moveFlag = false;
    short minOffset = PAGE_SIZE;
    for (short i = 0; i < slotCount; ++i) {
        short tmpOffset = this->getRecordOffset(page, i);
        minOffset = tmpOffset < minOffset ? tmpOffset : minOffset;
        if (tmpOffset < recordOffset) {
            moveFlag = true;
            this->setRecordOffset(page, i, tmpOffset + distance);
        }
    }
    short len = recordOffset - minOffset;
    if (moveFlag && len != 0) {
        char *cache = (char *) malloc(len);
        memcpy(cache, (char *) page + minOffset, len);
        memcpy((char *) page + minOffset + distance, cache, len);
        free(cache);
    }
    return 0;
}


RC RecordBasedFileManager::insertRecord(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                        const void *data, RID &rid) {
    char *offsets = (char *) malloc(recordDescriptor.size() * sizeof(short));
    short recordSize = parseRecord(recordDescriptor, data, offsets);

    // since we may need to place tombstone when update, so recode size have to be >= sizeof(RID)
    if (recordSize < sizeof(RID)) recordSize = sizeof(RID);
    void *page = malloc(PAGE_SIZE);
    unsigned pageCount = fileHandle.getNumberOfPages();

    //read freeList
    auto *freeList = (short *) fileHandle.freeListData;
    int insertPageNum = -1;
    if (pageCount > 0) {
        for (int i = pageCount - 1; i >= 0; i--) {
            if (freeList[i] >= recordSize + sizeof(short) * 2) {
                insertPageNum = i;
                break;
            }
        }
    }

    short freeSpace;
    if (insertPageNum == -1) { //no qualified page, need to append a new page
        this->setRecordCount(page, 0);
        this->setSlotCount(page, 0);
        freeSpace = PAGE_SIZE - 2 * sizeof(short);

        short insertOffset = this->getInsertOffset(page, freeSpace, recordSize);
        int fieldCount = recordDescriptor.size();
        this->insertFormatedRecord(page, insertOffset, fieldCount, data, offsets, recordSize);
        this->setRecordOffset(page, 0, insertOffset);
        this->setRecordSize(page, 0, recordSize);
        this->setRecordCount(page, 1);
        this->setSlotCount(page, 1);

        fileHandle.appendPage(page);

        rid.pageNum = pageCount;
        rid.slotNum = 0;

        freeSpace = freeSpace - recordSize - 2 * sizeof(short);
    } else {
        fileHandle.readPage(insertPageNum, page);
        short recordCount = this->getRecordCount(page);
        short slotCount = this->getSlotCount(page);
        freeSpace = freeList[insertPageNum];

        short slotNum = this->findUnusedSlot(page);
        short insertOffset = this->getInsertOffset(page, freeSpace, recordSize);
        int fieldCount = recordDescriptor.size();
        this->insertFormatedRecord(page, insertOffset, fieldCount, data, offsets, recordSize);
        this->setRecordOffset(page, slotNum, insertOffset);
        this->setRecordSize(page, slotNum, recordSize);
        recordCount += 1;
        this->setRecordCount(page, recordCount);
        slotCount = slotNum == slotCount ? slotCount + 1 : slotCount;
        this->setSlotCount(page, slotCount); //if a slot is added
        fileHandle.writePage(insertPageNum, page);

        rid.pageNum = insertPageNum;
        rid.slotNum = slotNum;
        if (slotCount == slotNum + 1) { //if a slot is added
            freeSpace = freeSpace - recordSize - 2 * sizeof(short);
        } else {
            freeSpace = freeSpace - recordSize;
        }
    }


    //re assign in case the address of freeListData is change, e.g. extend it's capacity
    freeList = (short *) fileHandle.freeListData;
    if (insertPageNum == -1)
        freeList[pageCount] = freeSpace;
    else
        freeList[insertPageNum] = freeSpace;


    free(page);
    free(offsets);
    return 0;
}

RC RecordBasedFileManager::readRecord(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                      const RID &rid, void *data) {
    if (rid.pageNum >= fileHandle.getNumberOfPages()) return -1;
    void *page = malloc(PAGE_SIZE);
    fileHandle.readPage(rid.pageNum, page);

    short recordOffset = this->getRecordOffset(page, rid.slotNum);
    short recordSize = this->getRecordSize(page, rid.slotNum);

    int res = OK;
    if (recordSize == Unused) {
        res = -1;
    } else if (recordSize == Tombstone) { //if it's tombstone
        RID tombstoneRid;
        memcpy(&tombstoneRid, (char *) page + recordOffset, sizeof(RID));
        res = readRecord(fileHandle, recordDescriptor, tombstoneRid, data);
    } else { //else we direct read it
        int fieldCount = recordDescriptor.size();
        int nullFlagSize = this->getNullFlagSize(fieldCount);
        memcpy((char *) data, (char *) page + recordOffset, nullFlagSize);
        short headerSize = nullFlagSize + fieldCount * sizeof(short);
        memcpy((char *) data + nullFlagSize, (char *) page + recordOffset + headerSize,
               recordSize - headerSize);
    }
    free(page);
    return res;
}

RC RecordBasedFileManager::deleteRecord(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                        const RID &rid) {

    if (rid.pageNum >= fileHandle.getNumberOfPages()) return -1;
    void *page = malloc(PAGE_SIZE);
    fileHandle.readPage(rid.pageNum, page);

    auto *freeList = (short *) fileHandle.freeListData;
    short freeSpace = freeList[rid.pageNum];

    short recordCount = this->getRecordCount(page);
    short recordOffset = this->getRecordOffset(page, rid.slotNum);
    short recordSize = this->getRecordSize(page, rid.slotNum);

    if (recordSize == Unused)
        return -1;
    else if (recordSize == Tombstone) { //if it's tombstone
        RID tombstoneRid;
        memcpy(&tombstoneRid, (char *) page + recordOffset, sizeof(RID));
        deleteRecord(fileHandle, recordDescriptor, tombstoneRid);
    } else { //else we direct delete it
        this->shiftRecord(page, recordOffset, recordSize);
        this->setRecordSize(page, rid.slotNum, Unused);
        this->setRecordCount(page, recordCount - 1);
        freeSpace += recordSize;
        fileHandle.writePage(rid.pageNum, page);
    }

    //maintain freeList
    freeList[rid.pageNum] = freeSpace;

    free(page);
    return 0;
}

RC RecordBasedFileManager::printRecord(const std::vector<Attribute> &recordDescriptor, const void *data) {
    int fieldCount = recordDescriptor.size();
    int nullFlagSize = this->getNullFlagSize(fieldCount);
    int dataPtr = 0;
    bool nullBit;
    auto *nullFlags = (unsigned char *) malloc(nullFlagSize);
    memcpy(nullFlags, (char *) data + dataPtr, nullFlagSize);
    dataPtr += nullFlagSize;
    for (int i = 0; i < fieldCount; i++) {
        // Add handler for null flags larger than 1 byte
        int bytePos = i / 8;
        int bitPos = i % 8;
        nullBit = nullFlags[bytePos] & (unsigned) 1 << (unsigned) (7 - bitPos);
        Attribute attr = recordDescriptor[i];
        cout << attr.name << ": ";
        AttrValue attrValue;
        if (!nullBit) {
            attrValue.readAttr(attr.type, (char *) data + dataPtr);
            attrValue.printSelf();
            cout << " | ";
            dataPtr += attrValue.length;
        } else {
            cout << "Null | ";
        }
    }
    cout << endl;
    free(nullFlags);
    return 0;
}

RC RecordBasedFileManager::updateRecord(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                        const void *data, const RID &rid) {
    if (rid.pageNum >= fileHandle.getNumberOfPages()) return -1;

    void *page = malloc(PAGE_SIZE);
    fileHandle.readPage(rid.pageNum, page);

    short recordOffset = this->getRecordOffset(page, rid.slotNum);
    short recordSize = this->getRecordSize(page, rid.slotNum);

    if (recordSize == Unused) return -1;

    int fieldCount = recordDescriptor.size();
    char *offsets = (char *) malloc(fieldCount * sizeof(short));
    short newSize = parseRecord(recordDescriptor, data, offsets);
    // since we may need to place tombstone when update, so recode size have to be >= sizeof(RID)
    if (newSize < sizeof(RID)) newSize = sizeof(RID);

    auto *freeList = (short *) fileHandle.freeListData;
    short freeSpace = freeList[rid.pageNum];
    short newFreeSpace = freeSpace + recordSize - newSize;
    if (newFreeSpace > 0) {
        short distance = recordSize - newSize;
        this->shiftRecord(page, recordOffset, distance);
        this->insertFormatedRecord(page, recordOffset + distance, fieldCount, data, offsets, newSize);

        this->setRecordOffset(page, rid.slotNum, recordOffset + distance);
        this->setRecordSize(page, rid.slotNum, newSize);

    } else {
        //insert record, since current page don't have enough space, this record won't be in current page
        RID insertRid;
        insertRecord(fileHandle, recordDescriptor, data, insertRid);

        int tombstoneSize = sizeof(RID);
        short distance = recordSize - tombstoneSize;
        this->shiftRecord(page, recordOffset, distance);
        memcpy((char *) page + recordOffset + distance, &insertRid, tombstoneSize);

        this->setRecordOffset(page, rid.slotNum, recordOffset + distance);
        this->setRecordSize(page, rid.slotNum, Tombstone);
        newFreeSpace = freeSpace + recordSize - tombstoneSize;
    }

    fileHandle.writePage(rid.pageNum, page);

    //maintain freeList, re assign in case the address of freeListData is change, e.g. extend it's capacity
    freeList = (short *) fileHandle.freeListData;
    freeList[rid.pageNum] = newFreeSpace;

    free(page);
    free(offsets);

    return 0;
}

RC RecordBasedFileManager::readAttribute(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                         const RID &rid, const std::string &attributeName, void *data) {
    if (rid.pageNum >= fileHandle.getNumberOfPages()) return -1;
    void *page = malloc(PAGE_SIZE);
    fileHandle.readPage(rid.pageNum, page);

    short recordOffset = this->getRecordOffset(page, rid.slotNum);
    short recordSize = this->getRecordSize(page, rid.slotNum);

    int fieldCount = recordDescriptor.size();
    int attrIndex = 0;
    for (attrIndex = 0; attrIndex < fieldCount; attrIndex++) {
        if (recordDescriptor[attrIndex].name == attributeName) {
            break;
        }
    }

    if (recordSize == Unused) {
        return -1;
    } else if (recordSize == Tombstone) { //if it's tombstone
        RID tombstoneRid;
        memcpy(&tombstoneRid, (char *) page + recordOffset, sizeof(RID));
        readAttribute(fileHandle, recordDescriptor, tombstoneRid, attributeName, data);
    } else { //else we direct read it

        int nullFlagSize = this->getNullFlagSize(fieldCount);
        short pagePtr = recordOffset;
        short offset, prevOffset;
        this->getAttrOffset(page, pagePtr, fieldCount, nullFlagSize, attrIndex, offset, prevOffset);
        short sz = offset - prevOffset;
        bool nullBit;
        if (sz == 0) {
            nullBit = false;
            memcpy(data, &nullBit, sizeof(bool));
        } else {
            nullBit = true;
            memcpy(data, &nullBit, sizeof(bool));
            memcpy((char *) data + sizeof(bool), (char *) page + pagePtr + prevOffset, sz);
        }
    }
    free(page);
    return 0;
}

RC RecordBasedFileManager::scan(FileHandle &fileHandle, const std::vector<Attribute> &recordDescriptor,
                                const std::string &conditionAttribute, const CompOp compOp, const void *value,
                                const std::vector<std::string> &attributeNames, RBFM_ScanIterator &rbfm_ScanIterator) {
    return rbfm_ScanIterator.init(fileHandle, recordDescriptor, conditionAttribute, compOp, value, attributeNames);
}

RC RBFM_ScanIterator::init(FileHandle &fileHandle, const vector<Attribute> &recordDescriptor,
                           const string &conditionAttribute,
                           const CompOp compOp, const void *value, const vector<string> &attrNames) {
    this->fileHandle = &fileHandle;
    this->recordDescriptor = recordDescriptor;
    this->compOp = compOp;
    this->value = value;
    this->attrNames = attrNames;

    int fieldCount = recordDescriptor.size();
    int i, j;
    // Get positions of attributes that are projected
    for (i = 0; i < attrNames.size(); i++) {
        for (j = 0; j < fieldCount; j++) {
            if (recordDescriptor[j].name == attrNames[i]) {
                this->attrIdx.emplace_back(j);
                break;
            }
        }
        if (j == fieldCount) {
            return -7; // AttributeNotFoundException
        }
    }

    for (i = 0; i < fieldCount; i++) {
        if (recordDescriptor[i].name == conditionAttribute) {
            this->condAttrIdx = i;
            break;
        }
    }
    if (i == fieldCount) {
        if (compOp == NO_OP) {
            this->condAttrIdx = -1;
        } else {
            return -7; // AttributeNotFoundException
        }
    }

    this->pageNum = 0;
    this->slotNum = 0;
    return 0;
}

RC RBFM_ScanIterator::getNextRecord(RID &rid, void *data) {
    RecordBasedFileManager &rbfm = RecordBasedFileManager::instance();
    void *page = malloc(PAGE_SIZE);
    this->fileHandle->readPage(this->pageNum, page);
    bool satisfied = false;
    while (true) {
        short slotCount = rbfm.getSlotCount(page);
        if (this->slotNum > slotCount - 1) {
            this->pageNum++;
            if (this->pageNum > this->fileHandle->getNumberOfPages() - 1) {
                free(page);
                return RBFM_EOF;
            } else {
                this->fileHandle->readPage(this->pageNum, page);
                this->slotNum = 0;
            }
        }
        rid.pageNum = this->pageNum;
        rid.slotNum = this->slotNum;

        short recordOffset = rbfm.getRecordOffset(page, rid.slotNum);
        short recordSize = rbfm.getRecordSize(page, rid.slotNum);

        this->slotNum++;
        if (recordSize == Unused) {
            continue;
        } else if (recordSize == Tombstone) { //if it's tombstone
            continue;
        } else { //else we direct read it
            short pagePtr = recordOffset;
            int fieldCount = this->recordDescriptor.size();
            short nullFlagSize = rbfm.getNullFlagSize(fieldCount);
            if (this->condAttrIdx == -1) {
                satisfied = true;
            } else {
                short offset, prevOffset;
                rbfm.getAttrOffset(page, pagePtr, fieldCount, nullFlagSize,
                                   this->condAttrIdx, offset, prevOffset);
                short sz = offset - prevOffset;
                if (sz > 0) {
                    void *checkValue = malloc(sz);
                    memcpy((char *) checkValue, (char *) page + pagePtr + prevOffset, sz);
                    satisfied = this->checkSatisfied(checkValue);
                    free(checkValue);
                } else {
                    continue;
                }
            }
            if (satisfied) {
                int newNullFlagsSize = rbfm.getNullFlagSize(this->attrNames.size());
                unsigned char *newNullFlags = (unsigned char *) malloc(newNullFlagsSize);
                memset(newNullFlags, 0, newNullFlagsSize);
                short offset, prevOffset, sz;
                short dataPtr = newNullFlagsSize;
                for (int i = 0; i < this->attrNames.size(); i++) {
                    rbfm.getAttrOffset(page, pagePtr, fieldCount, nullFlagSize,
                                       this->attrIdx[i], offset, prevOffset);
                    sz = offset - prevOffset;

                    if (sz > 0) {
                        memcpy((char *) data + dataPtr, (char *) page + pagePtr + prevOffset, sz);
                        dataPtr += sz;
                    } else {
                        int nullByte = i / 8;
                        int nullBit = i % 8;
                        newNullFlags[nullByte] |= (1 << (7 - nullBit));
                    }
                }
                memcpy((char *) data, newNullFlags, newNullFlagsSize);
                free(newNullFlags);
                free(page);
                return 0;
            }
        }
    }
    return RBFM_EOF;
}

bool RBFM_ScanIterator::checkSatisfied(void *checkValue) {
    Attribute attr = this->recordDescriptor[this->condAttrIdx];
    AttrValue toCheck;
    toCheck.readAttr(attr.type, checkValue);
    AttrValue compValue;
    compValue.readAttr(attr.type, this->value);
    return AttrValue::compAttr(toCheck, compValue, this->compOp);

}

RBFM_ScanIterator::RBFM_ScanIterator() {
    this->pageNum = 0;
    this->slotNum = 0;
}

RC RBFM_ScanIterator::close() {
    this->pageNum = 0;
    this->slotNum = 0;
    this->attrIdx.clear();
    return 0;
}

void AttrValue::readAttr(AttrType attrType, const void *data) {
    int len = 0;
    char *s;
    this->type = attrType;
    if (data == nullptr) {
        this->length = 0;
        return;
    }
    switch (this->type) {
        case TypeVarChar:
            memcpy(&len, data, sizeof(int));
            s = (char *) malloc(len);
            memset(s, 0, len);
            memcpy(s, (char *) data + sizeof(int), len);
            this->vchar = string(s, len);
            this->length = sizeof(int) + len;
            free(s);
            break;
        case TypeInt:
            memcpy(&this->itg, data, sizeof(int));
            this->length = sizeof(int);
            break;
        case TypeReal:
            memcpy(&this->flt, data, sizeof(float));
            this->length = sizeof(float);
            break;
        default:
            break;
    }
}

void AttrValue::writeAttr(void *data) {
    int len = 0;
    switch (this->type) {
        case TypeVarChar:
            len = this->length - sizeof(int);
            memcpy(data, &len, sizeof(int));
            memcpy((char *) data + sizeof(int), this->vchar.c_str(), this->vchar.length());
            break;
        case TypeInt:
            memcpy(data, &this->itg, sizeof(int));
            break;
        case TypeReal:
            memcpy(data, &this->flt, sizeof(float));
            break;
        default:
            break;
    }
}

void AttrValue::printSelf() {
    switch (this->type) {
        case TypeInt:
            cout << this->itg;
            break;
        case TypeReal:
            cout << this->flt;
            break;
        case TypeVarChar:
            cout << this->vchar;
        default:
            break;
    }
}

bool AttrValue::compAttr(AttrValue left, AttrValue right, CompOp op) {
    assert(left.type == right.type);
    switch (op) {
        case EQ_OP:
            return left == right;
        case NE_OP:
            return left != right;
        case LT_OP:
            return left < right;
        case LE_OP:
            return left <= right;
        case GT_OP:
            return left > right;
        case GE_OP:
            return left >= right;
        default:
            return true;
    }
}
